package com.lc1993929.dubbo.boot.reference;

import com.alibaba.dubbo.config.*;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by LiuChang on 2017/9/12/012.
 */
public class DubboReferenceBeanPostProcessor implements BeanPostProcessor, PriorityOrdered, BeanFactoryAware, ApplicationContextAware {
    protected final Log logger = LogFactory.getLog(getClass());
    private ConfigurableListableBeanFactory beanFactory;
    private ApplicationContext applicationContext;
    private final Map<Type, Object> cacheOfDubboReference = new ConcurrentHashMap<Type, Object>();//dubbo缓存

    /**
     * 实现{@link BeanPostProcessor}接口的方法，此方法可以在spring实例化bean之前做一些操作。
     * 这里会在实例化bean之前将生成被{@link Reference}注释了的字段和方法所对应的{@link ReferenceBean}注入到对应的字段和方法参数中
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
        final Class<?> beanClass = bean.getClass();
        Class<?> targetClass = bean.getClass();
        do {
            // 属性
            //对目标类targetClass中的每一个字段首先进行筛选Filter。
            //然后对筛选通过的字段Field执行回调方法Callback
            ReflectionUtils.doWithFields(
                    //目标类
                    targetClass,
                    //对通过了筛选的字段Field执行回调方法
                    new ReflectionUtils.FieldCallback() {
                        public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                            //再次判断该字段是否有Reference注解
                            Reference reference = AnnotatedElementUtils.findMergedAnnotation(field, Reference.class);
                            if (reference == null) {
                                return;
                            }

                            Object value = getBean(field.getType());
                            if (value == null && (value = cacheOfDubboReference.get(field.getType())) == null) {
                                //如果spring中没有且dubbo缓存中也没有该类型的bean，则根据接口名生成对应的dubbo中的ReferenceBean并返回
                                value = refer(reference, field.getType());
                            }
                            if (value != null) {
                                cacheOfDubboReference.put(field.getType(), value);//缓存ReferenceBean
                                if (getBean(field.getType()) == null) {
                                    beanFactory.registerSingleton(field.getType().getName(), value);//将生成的ReferenceBean注册到spring容器中且唯一
                                }
                                ReflectionUtils.makeAccessible(field);//设置字段可被访问
                                field.set(bean, value);//将ReferenceBean注入到字段中
                            }

                        }
                    },
                    //执行筛选方法，筛选出被Reference注解的字段Field
                    new ReflectionUtils.FieldFilter() {
                        public boolean matches(Field field) {
                            return field.isAnnotationPresent(Reference.class);
                        }
                    }
            );


            // 方法
            //对目标类的方法执行给定的回调方法
            ReflectionUtils.doWithLocalMethods(
                    targetClass,
                    new ReflectionUtils.MethodCallback() {
                        public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                            //比较具体的实现方法和接口中的方法的参数和返回类型是否一致，如果不一致则直接返回
                            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
                            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                                return;
                            }

                            AnnotationAttributes ann = AnnotatedElementUtils.getMergedAnnotationAttributes(bridgedMethod, Reference.class);
                            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
                                //如果该方法被Reference注解注释了，且方法和给定的class类里的方法相同
                                if (Modifier.isStatic(method.getModifiers())) {
                                    //如果方法是静态的，不支持
                                    if (logger.isWarnEnabled()) {
                                        logger.warn("Reference annotation is not supported on static methods: " + method);
                                    }
                                    return;
                                }
                                //如果方法没有参数，发出警告
                                if (method.getParameterTypes().length == 0) {
                                    if (logger.isWarnEnabled()) {
                                        logger.warn("Reference annotation should only be used on methods with parameters: " + method);
                                    }
                                }
                                //获取方法的参数
                                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);

                                Reference reference = AnnotatedElementUtils.findMergedAnnotation(method, Reference.class);
                                //从spring容器中获取参数对应类型的bean
                                Object value = getBean(pd.getPropertyType());
                                if (value == null && (value = cacheOfDubboReference.get(pd.getPropertyType())) == null) {
                                    //如果spring容器中没有该bean且dubbo缓存中也没有，则生成对应的ReferenceBean
                                    value = refer(reference, pd.getPropertyType());
                                }
                                if (value != null) {
                                    cacheOfDubboReference.put(pd.getPropertyType(), value);
                                    if (getBean(pd.getPropertyType()) == null) {
                                        beanFactory.registerSingleton(pd.getPropertyType().getName(), value);
                                    }
                                }

                                //将对应的ReferenceBean注入到方法的参数中
                                Object[] arguments;
                                Class<?>[] paramTypes = method.getParameterTypes();
                                arguments = new Object[paramTypes.length];
                                DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];
                                Set<String> autowiredBeans = new LinkedHashSet<String>(paramTypes.length);
                                TypeConverter typeConverter = beanFactory.getTypeConverter();
                                for (int i = 0; i < arguments.length; i++) {
                                    MethodParameter methodParam = new MethodParameter(method, i);
                                    DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, true);
                                    currDesc.setContainingClass(beanClass);
                                    descriptors[i] = currDesc;
                                    try {
                                        Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
                                        arguments[i] = arg;
                                    } catch (BeansException ex) {
                                        throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
                                    }
                                }
                                if (arguments != null) {
                                    ReflectionUtils.makeAccessible(method);
                                    try {
                                        method.invoke(bean, arguments);
                                    } catch (InvocationTargetException e) {
                                        throw new BeanCreationException(
                                                "Injection of @Reference dependencies failed for class [" + beanClass + "]", e);
                                    }
                                }

                            }

                        }
                    });
            targetClass = targetClass.getSuperclass();
        } while (targetClass != null && targetClass != Object.class);
        return bean;
    }

    /**
     * 实现{@link BeanPostProcessor}接口的方法，此方法可以在spring实例化bean之后做一些操作。
     * 这里没有进行任何操作
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * 实现{@link PriorityOrdered}接口的方法
     * 处理接口实现类的优先级问题，这里是将本类的优先级设为最低
     *
     * @return
     */
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    /**
     * 实现{@link BeanFactoryAware}接口的方法。
     * 通过该方法可以获取spring的bean工厂，就可以获得spring生产的bean了
     *
     * @param beanFactory
     * @throws BeansException
     */
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException(
                    "DubboReferenceBeanPostProcessor requires a ConfigurableListableBeanFactory");
        }
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    /**
     * 实现{@link ApplicationContextAware}接口的方法，通过该方法可以获得spring的环境。方便生成{@link ReferenceBean}时使用
     *
     * @param applicationContext
     * @throws BeansException
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 根据接口名生成对应的{@link ReferenceBean}并返回
     *
     * @param reference
     * @param referenceClass
     * @return
     */
    private Object refer(Reference reference, Class<?> referenceClass) {
        //获取Reference注解所注解的字段的接口名，如果没有接口就报错
        String interfaceName;
        if (!"".equals(reference.interfaceName())) {
            interfaceName = reference.interfaceName();
        } else if (!void.class.equals(reference.interfaceClass())) {
            interfaceName = reference.interfaceClass().getName();
        } else if (referenceClass.isInterface()) {
            interfaceName = referenceClass.getName();
        } else {
            throw new IllegalStateException("The @Reference undefined interfaceClass or interfaceName, and the property type " + referenceClass
                    .getName() + " is not a interface.");
        }

        //生成ReferenceBean并返回
        ReferenceBean<?> referenceBean = new ReferenceBean<Object>(reference);
        referenceBean.setApplicationContext(this.applicationContext);
        referenceBean.setInterface(interfaceName);
        if (void.class.equals(reference.interfaceClass())
                && "".equals(reference.interfaceName())
                && referenceClass.isInterface()) {
            referenceBean.setInterface(referenceClass);
        }
        if (reference.registry() != null && reference.registry().length > 0) {
            List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
            for (String registryId : reference.registry()) {
                if (registryId != null && registryId.length() > 0) {
                    registryConfigs.add(beanFactory.getBean(registryId, RegistryConfig.class));
                }
            }
            referenceBean.setRegistries(registryConfigs);
        }
        if (reference.consumer() != null && reference.consumer().length() > 0) {
            referenceBean.setConsumer(beanFactory.getBean(reference.consumer(), ConsumerConfig.class));
        }
        if (reference.monitor() != null && reference.monitor().length() > 0) {
            referenceBean.setMonitor(beanFactory.getBean(reference.monitor(), MonitorConfig.class));
        }
        if (reference.application() != null && reference.application().length() > 0) {
            referenceBean.setApplication(beanFactory.getBean(reference.application(), ApplicationConfig.class));
        }
        if (reference.module() != null && reference.module().length() > 0) {
            referenceBean.setModule(beanFactory.getBean(reference.module(), ModuleConfig.class));
        }
        if (reference.consumer() != null && reference.consumer().length() > 0) {
            referenceBean.setConsumer(beanFactory.getBean(reference.consumer(), ConsumerConfig.class));
        }
        try {
            referenceBean.afterPropertiesSet();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        return referenceBean.get();
    }

    /**
     * 从给定的spring容器{@link BeanFactory}中获取指定类型的唯一的bean
     *
     * @param clazz
     * @param <T>
     * @return
     */
    private <T> T getBean(Class<T> clazz) {
        try {
            return BeanFactoryUtils.beanOfType(beanFactory, clazz);
        } catch (BeansException ex) {
            return null;
        }
    }
}
